package business;

import entity.WeatherForecast;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.xml.ws.http.HTTPException;
import utils.DateService;

@Stateless
public class WeatherForecastRetriever implements Serializable {

    public static final String fiveDaysForecasts = "http://api.openweathermap.org/data/2.5/forecast";
    public static final String sixteenDaysForecasts = "http://api.openweathermap.org/data/2.5/forecast/daily";

    private String resourceURL;

    public WeatherForecastRetriever() {
        resourceURL = fiveDaysForecasts;
    }

    public void setResourceURL(String url) {
        resourceURL = url;
    }

    public WeatherForecast getForecast(String city, String countryCode, Date date) {
        JsonObject response = getResponse(resourceURL + "?q=" + city + "," + countryCode);
        if (response != null) {
            int statusCode = Integer.parseInt(response.getString("cod"));
            if (statusCode != 200) {
                throw new HTTPException(statusCode);
            }
            return getForecastByDate(response, date);
        }
        return null;
    }

    private JsonObject getResponse(String url) {
        try {
            URL queryUrl = new URL(url);
            try (JsonReader jsonReader = Json.createReader(queryUrl.openStream())) {
                return jsonReader.readObject();
            } catch (Exception ex) {
                Logger.getLogger(WeatherForecastRetriever.class.getName()).log(Level.SEVERE, null, ex);
            }
        } catch (MalformedURLException ex) {
            Logger.getLogger(WeatherForecastRetriever.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    private WeatherForecast getForecastByDate(JsonObject response, Date date) {
        WeatherForecast forecast = null;
        JsonObject cityObject = response.getJsonObject("city");
        JsonArray forecastList = response.getJsonArray("list");
        if (forecastList != null) {
            int forecastIndex = -1;
            Date d = null;
            for (int i = 0; i < forecastList.size(); i++) {
                long time = forecastList.getJsonObject(i).getJsonNumber("dt").longValueExact();
                Date forecastDate = new Date(time * 1000);
                if (date.equals(forecastDate)
                        || (date.after(forecastDate) && date.before(DateService.hoursLater(forecastDate, 3)))) {
                    d = forecastDate;
                    forecastIndex = i;
                    break;
                }
            }
            if (forecastIndex != -1) {
                forecast = new WeatherForecast();
                JsonObject jsonForecast = forecastList.getJsonObject(forecastIndex);
                JsonObject mainObject = jsonForecast.getJsonObject("main");
                JsonObject weatherObject = jsonForecast.getJsonArray("weather").getJsonObject(0);
                JsonObject snowObject = jsonForecast.getJsonObject("snow");
                JsonObject rainObject = jsonForecast.getJsonObject("rain");
                forecast.setDate(d);
                if (cityObject != null) {
                    forecast.setCity(cityObject.getString("name"));
                    forecast.setCountryCode(cityObject.getString("country"));
                }
                if (mainObject != null) {
                    forecast.setTemperature(mainObject.getInt("temp") - 273);
                }
                if (weatherObject != null) {
                    forecast.setWeatherType(weatherObject.getString("main"));
                    forecast.setWeatherDescription(weatherObject.getString("description"));
                    forecast.setWeather(WeatherForecast.parseOrNone(
                            forecast.getWeatherType(), forecast.getWeatherDescription()));
                    forecast.setWeatherIcon(weatherObject.getString("icon"));
                }
                if (rainObject != null) {
                    forecast.setRain(rainObject.getInt("3h"));
                }
                if (snowObject != null) {
                    forecast.setSnow(snowObject.getInt("3h"));
                }
            }
        }
        return forecast;
    }

    private List<WeatherForecast> getPartialForecastsFrom(JsonObject response, Date date) {
        List<WeatherForecast> forecasts = new ArrayList<>();
        JsonArray forecastList = response.getJsonArray("list");
        if (forecastList != null) {
            for (int i = 0; i < forecastList.size(); i++) {
                JsonObject jsonForecast = forecastList.getJsonObject(i);
                long time = jsonForecast.getJsonNumber("dt").longValueExact();
                Date forecastDate = new Date(time * 1000);
                if (forecastDate.after(date)) {
                    JsonObject weatherObject = jsonForecast.getJsonArray("weather").getJsonObject(0);
                    WeatherForecast forecast = new WeatherForecast();
                    forecast.setDate(forecastDate);
                    forecast.setWeatherType(weatherObject.getString("main"));
                    forecast.setWeatherDescription(weatherObject.getString("description"));
                    forecast.setWeather(WeatherForecast.parseOrNone(
                            forecast.getWeatherType(), forecast.getWeatherDescription()));
                    forecasts.add(forecast);
                }
            }
        }
        return forecasts;
    }

    public Date getNextGoodConditionsDay(String city, String country, Date date, String badWeatherThreshold) {
        Date goodDay = null;
        JsonObject response = getResponse(resourceURL + "?q=" + city + "," + country);
        if (response != null) {
            List<WeatherForecast> forecasts = getPartialForecastsFrom(response, date);
            for (WeatherForecast forecast : forecasts) {
                if (!forecast.isBad(badWeatherThreshold)) {
                    goodDay = forecast.getDate();
                    break;
                }
            }
        }
        return goodDay;
    }
}
